#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#include <limits.h>

#define ONEMEGABYTE  1048576 
#define BUFFSIZE     ONEMEGABYTE


long int uconv(const char *num)
{
   long int convnum;
  
   switch (num[strlen(num) - 1]) {
      case 'G':
         convnum = atof(num)*1000000000;
         break;
      case 'M':
         convnum = atof(num)*1000000;
         break;
      case 'K':
         convnum = atof(num)*1000;
         break;
      default:
         convnum = atof(num);
   }

   return convnum;
}

int
main(int argc, char *argv[])
{
   int            i, j, n, fd, opt;
   unsigned int   blocksize; 
   unsigned long  ntotal, npartial, dataw, datas, filesize, mfilesize;
   char           buf[BUFFSIZE], pathname[PATH_MAX];
   struct timespec tstart, tstop;
   double         iotime, iospeed;
   char *         outfilename = ".tmpoutXXXXXX";

   /* default paremeters */
   blocksize = 4096;
   datas = 400*ONEMEGABYTE;
   mfilesize = 0;
   dataw = 10.1*datas;
   strcpy(pathname, outfilename);

   /* set optional parameters from command line */
   while ((opt = getopt(argc, argv, "b:f:hm:p:s:vw:")) != -1) {
      switch (opt) {
         case 'b':
            blocksize = uconv(optarg);
            if(blocksize > BUFFSIZE) {
               fprintf(stderr, "blocksize must be < %d\n", BUFFSIZE);
               exit(EXIT_FAILURE);
            }
            break;
         case 'h':
            printf("Usage: %s [-b blocksize] [-h] [-m mfilesize] "
                  "[-p path] [-s datas] [-v] [-w dataw]\n", argv[0]);
            printf(" b  Write buffer size (bytes). "
                  "Default: %u\n", blocksize);
            printf(" h  Print this help and exit.\n");
            printf(" m  Maximum file size (0 means unlimited). "
                  "After reaching maximum file size\n"
                  "    data will be overwritten. "
                  "Default: %lu\n", mfilesize);
            printf(" p  Path I/O file operation to take place.\n");
            printf(" s  Ammount of data (bytes) to trigger statistic output. "
                  "Default: %lu\n", datas);
            printf(" v  Print version and exit.\n");
            printf(" w  Ammount of data (bytes) to write. "
                  "Default: %lu\n", dataw);
            printf("SI units can be used (G, M, K).\n");
            exit(EXIT_SUCCESS);
            break;
         case 'm':
            mfilesize = uconv(optarg);
            break;
         case 'p':
            strncpy(pathname, optarg, PATH_MAX-strlen(outfilename)-1);
            if (pathname[strlen(pathname)-1] == '/')
               strncat(pathname, outfilename, strlen(outfilename)+1);
            else {
               pathname[strlen(pathname)] = '/';
               strncat(pathname, outfilename, strlen(outfilename)+2);
            }
            break;
         case 's':
            datas = uconv(optarg);
            break;
         case 'v':
            printf("%s Version %s\n", argv[0], VERSION);
            exit(EXIT_SUCCESS);
            break;
         case 'w':
            dataw = uconv(optarg);
            break;
         default:
            fprintf(stderr, "Invalid option. Use -h for help.\n");
            exit(EXIT_FAILURE);
      }
   }

   /* Create a temporary file and unlink it */
   if ((fd = mkstemp(pathname)) < 0)
      error_at_line(EXIT_FAILURE, errno, __FILE__, __LINE__,
              "FATAL ERROR");
   if ((unlink(pathname)) < 0)
      error_at_line(EXIT_FAILURE, errno, __FILE__, __LINE__, 
              "FATAL ERROR");

   /* Fill the write buffer with a sequence */
   for (i = 0, j = 0; i < blocksize; i++, j++) {
      buf[i] = j;
      if(j == 255)
         j = 0;
   }

   /* loop for write test */
   if (clock_gettime(CLOCK_REALTIME, &tstart) < 0)
      error_at_line(EXIT_FAILURE, errno, __FILE__, __LINE__,
              "FATAL ERROR");

   ntotal = 0;
   npartial = 0;
   filesize = 0;
   while (ntotal < dataw) {

      /* Write data to disk */
      if ((n = write(fd, buf, blocksize)) < 0)
         error_at_line(EXIT_FAILURE, errno, __FILE__, __LINE__,
                 "FATAL ERROR");

      /* Check for statistic output */
      if (npartial > datas) {

         if (clock_gettime(CLOCK_REALTIME, &tstop) < 0)
            error_at_line(EXIT_FAILURE, errno, __FILE__, __LINE__,
                  "FATAL ERROR");

         iotime = ( tstop.tv_sec - tstart.tv_sec )
                 + ( tstop.tv_nsec - tstart.tv_nsec )
                 / 1000000000.0;
         iospeed = npartial / iotime / 1000000.0;

         printf("I/O write time: %f s\n", iotime);
         printf("I/O write speed: %f MByte/s\n", iospeed);

         if (clock_gettime(CLOCK_REALTIME, &tstart) < 0)
            error_at_line(EXIT_FAILURE, errno, __FILE__, __LINE__,
                  "FATAL ERROR");

         npartial = 0;
      } else
         npartial += n;

      /* Check for file size */
      if (mfilesize) {
         if (filesize > mfilesize) {
            if (lseek(fd, 0, SEEK_SET) < 0)
               error_at_line(EXIT_FAILURE, errno, __FILE__, __LINE__,
                     "FATAL ERROR");
            filesize = 0;
         } else
            filesize += n;
      }

      ntotal += n;
   }

   /* loop for read test */
   if (lseek(fd, 0, SEEK_SET) < 0)
      error_at_line(EXIT_FAILURE, errno, __FILE__, __LINE__,
            "FATAL ERROR");

   if (clock_gettime(CLOCK_REALTIME, &tstart) < 0)
      error_at_line(EXIT_FAILURE, errno, __FILE__, __LINE__,
              "FATAL ERROR");

   ntotal = 0;
   npartial = 0;
   while (ntotal < dataw) {

      /* Read data from disk */
      if ((n = read(fd, buf, blocksize)) < 0)
         error_at_line(EXIT_FAILURE, errno, __FILE__, __LINE__,
                 "FATAL ERROR");

      /* Check for statistic output */
      if (npartial > datas) {

         if (clock_gettime(CLOCK_REALTIME, &tstop) < 0)
            error_at_line(EXIT_FAILURE, errno, __FILE__, __LINE__,
                  "FATAL ERROR");

         iotime = ( tstop.tv_sec - tstart.tv_sec )
                 + ( tstop.tv_nsec - tstart.tv_nsec )
                 / 1000000000.0;
         iospeed = npartial / iotime / 1000000.0;

         printf("I/O read time: %f s\n", iotime);
         printf("I/O read speed: %f MByte/s\n", iospeed);

         if (clock_gettime(CLOCK_REALTIME, &tstart) < 0)
            error_at_line(EXIT_FAILURE, errno, __FILE__, __LINE__,
                  "FATAL ERROR");

         npartial = 0;
      } else
         npartial += n;

      /* Check for EOF */
      if (n == 0) {
         if (lseek(fd, 0, SEEK_SET) < 0)
            error_at_line(EXIT_FAILURE, errno, __FILE__, __LINE__,
                  "FATAL ERROR");
      }

      ntotal += n;
   }

   exit(EXIT_SUCCESS);
}
